# -*-coding:utf-8-*-

import hashlib
import json
import time
import urllib
from bson.objectid import ObjectId
from tornado.gen import coroutine, Return, Task
import settings
from handlers.base import BaseHandler
from pyrestful.rest import get
from utils.auth import authenticated
from utils.auth import generate_token
from utils.http import http_get
import urlparse
import base64

# TODO: 替换为登录确认界面
wx_auth_success_page = '''
<html>
    <head>
        <title>wechat auth success</title>
        <script src=http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js></script>
        <script src=http://res.wx.qq.com/open/js/jweixin-1.2.0.js></script>
        <script>
            $(document).ready(function() {
                function onBridgeReady() {
                    setTimeout(function() {
                        WeixinJSBridge.call('closeWindow');
                    }, 2000);
                }

                if (typeof WeixinJSBridge == "undefined") {
                    if (document.addEventListener) {
                        document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
                    } else if (document.attachEvent) {
                        document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
                        document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
                    }
                } else {
                    onBridgeReady();
                }
            });
        </script>
    </head>
    <body>
        <p>This page would close automatically in 2 seconds</p>
    </body>
</html>
'''

'''
在Url对应的处理方法中，获取传递的参数，进行加密/校验
流程如下：
1. 将token、timestamp、nonce三个参数进行字典序排序
2. 将三个参数字符串拼接成一个字符串进行sha1加密
3. 开发者获得加密后的字符串可与signature对比，标识该请求来源于微信

通过加密获取后台计算出的signature字符串；与微信传递过来的参数signature字符串进行比对；若一致,则校验通过，请原样返回echostr参数内容

GET /mp/verify_token?signature=43a091c013e37791e988748daae0a413a049ad8d&echostr=2972776364951719870&timestamp=1494863618&nonce=1863249364 HTTP/1.0
Host: wx
Connection: close
User-Agent: Mozilla/4.0
Accept: */*
Pragma: no-cache

timestamp = '1494863618'
nonce = '1863249364'
args = [timestamp, nonce, TOKEN]
args.sort()
hashlib.sha1(''.join(args)).hexdigest()
'''

WX_OP_API_PREFIX = 'https://open.weixin.qq.com'
WX_MP_API_PREFIX = 'https://api.weixin.qq.com'
STATE = '0a03cdde6285fa095df09a81110af673'


@coroutine
def wx_mp_api_sns_oauth2_access_token(app_id, secret, code):
    wx_mp_api_sns_oauth2_access_token_url = '%s/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code' % (
        WX_MP_API_PREFIX, app_id, secret, code)
    result = yield http_get(wx_mp_api_sns_oauth2_access_token_url)
    raise Return(result)


@coroutine
def wx_mp_api_sns_userinfo(access_token, openid):
    wx_mp_api_sns_userinfo_url = '%s/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN' % (
        WX_MP_API_PREFIX, access_token, openid)
    result = yield http_get(wx_mp_api_sns_userinfo_url)
    raise Return(result)


class WXMPHandler(BaseHandler):
    def __init__(self, application, request, **kwargs):
        super(WXMPHandler, self).__init__(application, request, **kwargs)

    # https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
    @coroutine
    def wx_mp_api_get_access_token(self):
        key_access_token = 'wx_mp_access_token_%s' % (self.app.get_wx_mp_app_id())
        result = yield Task(self.redis_client.exists, key_access_token)
        if result:
            result = yield Task(self.redis_client.get, key_access_token)
            raise Return(result)
        else:
            wx_mp_api_get_access_token_url = '%s/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s' % (
                WX_MP_API_PREFIX, self.app.get_wx_mp_app_id(), self.app.get_wx_mp_secret())
            response = yield http_get(wx_mp_api_get_access_token_url)
            self.logger.info('wx_mp_api_get_access_token: %s' % response.body)
            response = json.loads(response.body)
            access_token = response['access_token']
            expires_in = int(response['expires_in'])
            yield Task(self.redis_client.setex, key_access_token, int(expires_in * 0.95), access_token)
            raise Return(access_token)

    # https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
    @coroutine
    def wx_mp_api_get_ticket(self, ticket_type='jsapi'):
        key_ticket = 'wx_mp_ticket_%s_%s' % (ticket_type, self.app.get_wx_mp_app_id())
        result = yield Task(self.redis_client.exists, key_ticket)
        if result:
            result = yield Task(self.redis_client.get, key_ticket)
            raise Return(result)
        else:
            access_token = yield self.wx_mp_api_get_access_token()
            wx_mp_api_get_ticket_url = '%s/cgi-bin/ticket/getticket?access_token=%s&type=%s' % (
                WX_MP_API_PREFIX, access_token, ticket_type)
            response = yield http_get(wx_mp_api_get_ticket_url)
            response = json.loads(response.body)
            ticket = response['ticket']
            expires_in = int(response['expires_in'])
            Task(self.redis_client.setex, key_ticket, int(expires_in * 0.95), ticket)
            raise Return(ticket)

    # /mp/on_verify_token?appkey=APPKEY
    # @get(_path='/mp/on_verify_token', _produces=mediatypes.APPLICATION_JSON)
    @get(_path='/mp/on_verify_token')
    def on_verify_token(self):
        echostr = self.get_argument('echostr')
        signature = self.get_argument('signature')
        timestamp = self.get_argument('timestamp')
        nonce = self.get_argument('nonce')
        appkey = self.get_argument('appkey')
        verify_token = settings.APPS[appkey]['weixin']['mp']['verify_token']
        args = [timestamp, nonce, verify_token]
        args.sort()
        if hashlib.sha1(''.join(args)).hexdigest() == signature:
            self.finish(echostr)

    '''
    GET /mp/jsapi/config_args?url=URL_OF_PAGE_URLENCODED
    Response:
    {
        'appid': 'STRING',
        'timestamp': INT,
        'noncestr': 'STRING',
        'signature': 'STRING',
    }
    '''

    @get(_path='/mp/jsapi/config_args')
    @authenticated
    @coroutine
    def get_jsapi_config_args(self):
        url = self.get_argument('url')
        jsapi_ticket = yield self.wx_mp_api_get_ticket()
        args = {
            'jsapi_ticket': jsapi_ticket,
            'noncestr': hashlib.md5(str(ObjectId())).hexdigest(),
            'timestamp': int(time.time()),
            'url': url.split('#')[0],
        }
        keys = args.keys()
        keys.sort()
        args_string = ''
        for key in keys:
            args_string += '%s=%s&' % (key, args[key])
        sha1 = hashlib.sha1()
        sha1.update(args_string[:-1])
        signature = sha1.hexdigest()
        config_args = {
            'appid': self.app.get_wx_mp_app_id(),
            'timestamp': args['timestamp'],
            'noncestr': args['noncestr'],
            'signature': signature,
        }
        self.respond_success(config_args)

    def _redirect_with_cookies(self, redirect_uri, result):
        urlparsed = urlparse.urlparse(redirect_uri)
        if isinstance(result, dict) and 'access_token' in result:
            access_token = result['access_token']
            expires_at = result['expires_at']
            user_id = result['user_id']
            self.logger.info('urlparsed.netloc: %s, path: %s' % (urlparsed.netloc, self.request.path))
            self.set_cookie('access_token', access_token, expires=expires_at, domain=urlparsed.netloc)
            if '' == urlparsed.query:
                redirect_uri += '?user_id=%s' % user_id
            else:
                redirect_uri += '&user_id=%s' % user_id
        else:
            self.set_cookie('user_bind_info', result, domain=urlparsed.netloc)
        self.redirect(redirect_uri)

    # ?code=CODE&state=STATE
    @get(_path='/mp/on_auth')
    @coroutine
    def on_auth(self):
        appkey = self.get_argument('appkey')
        state = self.get_argument('state')
        if state != STATE:
            raise Exception('state check fail')
        code = self.get_argument('code')
        redirect_uri = self.get_argument('redirect_uri', None)
        wait_id = self.get_argument('wait_id', None)
        scope = self.get_argument('scope', 'snsapi_base')
        if 'snsapi_base' != scope and 'snsapi_userinfo' != scope:
            raise Exception('undefined auth scope')

        app = yield self.apps_dao.get_app(appkey)
        wx_app_id = app.get_wx_mp_app_id()
        wx_secret = app.get_wx_mp_secret()

        if self.prod:
            response = yield wx_mp_api_sns_oauth2_access_token(wx_app_id, wx_secret, code)
            self.logger.info(response.body)
            response = json.loads(response.body)
            wx_mp_openid = response['openid']
            wx_mp_access_token = response['access_token']
        else:
            wx_mp_openid = self.get_argument('wx_mp_openid')

        user = yield self.users_dao.get_user_by_wx_mp_openid(app, wx_mp_openid)
        if user is None:
            result = {
                'weixin': {
                    'mp': {
                        'openid': wx_mp_openid,
                    }
                }
            }
            if 'snsapi_userinfo' == scope and self.prod:
                response = yield wx_mp_api_sns_userinfo(wx_mp_access_token, wx_mp_openid)
                response = json.loads(response.body)
                if 'nickname' in response:
                    result['nickname'] = response['nickname']
                if 'headimgurl' in response:
                    result['avatar'] = response['headimgurl']
            self.logger.info('wx bind result: %s' % json.dumps(result))
            result = base64.urlsafe_b64encode(json.dumps(result))
        else:
            user_id = user.id_str
            access_token, expires_at = generate_token(app.appkey, user_id, self.request.remote_ip)
            result = {
                'access_token': access_token,
                'expires_at': expires_at,
                'user_id': user_id,
            }
        if redirect_uri is not None:
            self._redirect_with_cookies(redirect_uri, result)
        else:
            # For the case of authenticating to 3rd app through WX app.
            self.mqtornado.wait_response_complete(wait_id, json.dumps(result))
            self.finish(wx_auth_success_page)

    @get(_path='/mp/auth')
    @coroutine
    def auth(self):
        # 带 wait_id 与否决定授权后进行跳转或直接返回
        wait_id = self.get_argument('wait_id', None)
        appkey = self.get_argument('appkey')
        scope = self.get_argument('scope', 'snsapi_userinfo')
        if 'snsapi_base' != scope and 'snsapi_userinfo' != scope:
            raise Exception('undefined auth scope')
        wx_redirect_uri = '%s://%s/mp/on_auth?appkey=%s&scope=%s' % (
            self.request.protocol, self.request.host, appkey, scope)
        if wait_id is None:
            redirect_uri = self.get_argument('redirect_uri')
            redirect_uri = urllib.quote_plus(redirect_uri)
            wx_redirect_uri += '&redirect_uri=%s' % redirect_uri
        else:
            wx_redirect_uri += '&wait_id=%s' % wait_id
        self.logger.info(wx_redirect_uri)
        wx_redirect_uri = urllib.quote_plus(wx_redirect_uri)

        app = yield self.apps_dao.get_app(appkey)
        mp_app_id = app.get_wx_mp_app_id()

        wx_open_api_connect_oauth2_authorize_url = \
            '%s/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code' \
            '&scope=%s&state=%s#wechat_redirect' % (WX_OP_API_PREFIX, mp_app_id, wx_redirect_uri, scope, STATE)
        self.redirect(wx_open_api_connect_oauth2_authorize_url)
